#include "AABB.h"
#include <cfloat>
#include <climits>
#include "ray.h"
#include "Utility.h"
#include "GlobalDefine.h"

bool mray::Edge::operator < (const Edge& e)
{
	if(m_t == e.m_t) return (uchar)m_type < (uchar)e.m_type;
	else return m_t < e.m_t;
}

mray::AABB::AABB():
	m_min(vec3(FLT_MAX, FLT_MAX, FLT_MAX)), m_max(vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX)){ }

mray::AABB::AABB(const vec3& min, const vec3& max):
	m_min(min), m_max(max){}

mray::AABB mray::AABB::operator&(const AABB& b) const
{
	AABB box;
	for(int i = 0; i < 3; i++){
		box.m_max[i] = m_max[i]>b.m_max[i] ? m_max[i] : b.m_max[i];
		box.m_min[i] = m_min[i]<b.m_min[i] ? m_min[i] : b.m_min[i];
	}
	return box;
}

const mray::AABB& mray::AABB::operator&=(const AABB& b)
{
	for(uint i = 0; i < 3; i++){
		if(m_max[i] < b.m_max[i]) m_max[i] = b.m_max[i];
		if(m_min[i] > b.m_min[i]) m_min[i] = b.m_min[i];
	}
	return *this;
}

mray::AABB mray::AABB::operator & (const vec3& v) const
{
	AABB box(*this);
	for(uint i = 0; i < 3; i++){
		if(box.m_max[i] < v[i]) box.m_max[i] = v[i];
		if(box.m_min[i] > v[i]) box.m_min[i] = v[i];
	}
	return box;
}

const mray::AABB& mray::AABB::operator &= (const vec3& v)
{
	for(uint i = 0; i < 3; i++){
		if(m_max[i] < v[i]) m_max[i] = v[i];
		if(m_min[i] > v[i]) m_min[i] = v[i];
	}
	return *this;
}

mray::AXIS mray::AABB::GetMaxExtent()const
{
	vec3 d = m_max - m_min;
	return d.x>d.y ? (d.x>d.z ? X : Z) : (d.y>d.z ? Y : Z);
}

void mray::AABB::GetEdge(Edge& e, AXIS axis, Edge::TYPE type) const
{
	e.m_t = m_data[type][axis];
	e.m_type = type;
	e.m_aabb = this;
}

float mray::AABB::GetSurfaceArea() const
{
	vec3 d = m_max - m_min;
	return 2.0f*(d.x*d.y + (d.x+d.y)*d.z);
}

float mray::AABB::GetSurfaceArea(AXIS axis, float t, bool below) const
{
	AXIS otherAxis0 = (AXIS)(((uchar)axis+1)%3);
	AXIS otherAxis1 = (AXIS)(((uchar)axis+2)%3);
	vec3 d = m_max - m_min;
	if(below)
		return 2.f*(d[otherAxis0]*d[otherAxis1] + (t-m_min[axis])*(d[otherAxis0]+d[otherAxis1]));
	else
		return 2.f*(d[otherAxis0]*d[otherAxis1] + (m_max[axis]-t)*(d[otherAxis0]+d[otherAxis1]));
}

bool mray::AABB::Split(AXIS axis, float t, AABB& lowBox, AABB& highBox)const
{
	if(m_min[axis] < t && t < m_max[axis]){
		lowBox.m_min = m_min;
		lowBox.m_max = m_max;
		lowBox.m_max[axis] = t;
		highBox.m_min = m_min;
		highBox.m_max = m_max;
		highBox.m_min[axis] = t;
		return true;
	}else
		return false;
}

float mray::AABB::GetLengthWidthHeight() const
{
	float ret = 0.f;
	for(uint i = 0; i < 3; i++)
		ret += (m_max[i] - m_min[i]);
	return ret;
}

bool mray::AABB::Contain(const vec3& v) const
{
	return 
		m_min[X] <= v[X] && v[X] <= m_max[X] && 
		m_min[Y] <= v[Y] && v[Y] <= m_max[Y] && 
		m_min[Z] <= v[Z] && v[Z] <= m_max[Z];
}

bool mray::AABB::Hit(Ray& ray) const
{
	vec3 tmax, tmin;
	for(uint axis = 0; axis < 3; axis++){
		float tmp = 1.f/ray.m_dir[axis];
		if(ray.m_dir[axis] >= 0.f){
			tmax[axis] = (m_max[axis] - ray.m_ori[axis])*tmp;
			tmin[axis] = (m_min[axis] - ray.m_ori[axis])*tmp;
		}else{
			tmin[axis] = (m_max[axis] - ray.m_ori[axis])*tmp;
			tmax[axis] = (m_min[axis] - ray.m_ori[axis])*tmp;
		}
	}
	float fmax = Min(tmax.x, tmax.y, tmax.z);
	float fmin = Max(tmin.x, tmin.y, tmin.z);
	if(fmin < fmax){
		if(fmin > kEpsilon){
			if(ray.m_mint > fmin){
				ray.m_mint = fmin;
				ray.m_maxt = fmax;
				ray.m_onlyMin =false;
			}
			return true;
		}else if(fmax > kEpsilon){
			if(ray.m_mint > fmax){
				ray.m_mint = fmax;
				ray.m_maxt = fmin;
				ray.m_onlyMin = true;
			}
			ray.m_mint = fmax;
			return true;
		}
	}
	return false;
}

void mray::AABB::Fromat()
{
	float threshold = GetLengthWidthHeight()/9.f;
	for(uint i = 0; i < 3; i++){
		if(m_max[i]-m_min[i] < threshold){
			float mid = (m_min[i] + m_max[i])*.5f;
			m_max[i] = mid + threshold * .5f;
			m_min[i] = mid - threshold * .5f;
		}
	}
}

void mray::AABB::Reset()
{
	m_min = vec3(FLT_MAX, FLT_MAX, FLT_MAX);
	m_max = vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
}
